package ks.server.controllers.commands.admin.plugin;

import ks.framework.common.Message;
import ks.framework.communicator.Communicator;
import ks.framework.interfaces.IProcessMessage;
import ks.managers.plugin.Plugin;
import ks.managers.plugin.PluginManager;
import ks.managers.user.UserManager;
import ks.server.ServerContext;
import ks.server.ipc.Listener;
import ks.server.persist.PersistentPluginManager;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;

/**
 * Server-side controller for processing an admin-command to add a plugin
 * @author harry moreno
 *
 */
public class PluginInstallController implements IProcessMessage {

	/** Primary agent to whom we can communicate our messages. */
	Communicator com;
	
	/** regular expression indicating how to divide xml */
	String delims = "[/.]+";	//divides string by '/' and '.'
	
	/** Plugin directory where we will dump all plugins retrieved remotely. */
	public static final String pluginStorage = "Plugins";
	
	/** Attributes from XML string. */
	public static final String uriAtt = "uri";
	
	public PluginInstallController (Communicator com) {
		this.com = com;
	}
	/** 
	 * plugin-install Message comes in from Client.
	 * 
	 *  <plugin-install uri="http://www.cs.wpi.edu/~heineman/somePlaceElse.jar"/>
	 * @throws MalformedURLException 
	 */
	public boolean process(Message m){
		String originator = m.getOriginator();
		UserManager um = ServerContext.getUserManager();
		
		if (originator == null) {
			System.err.println("Originator is null.");
			return false;
		}
		if (!um.getUser(originator).isAdmin()) {
			Message r = Listener.nack(m, 4, "Access denied.");
			r.setRecipient(originator);
			com.distribute(r);
			return false;
		}
		
		Node pluginInstallNode = m.contents();
		
		if (pluginInstallNode == null || !pluginInstallNode.getNodeName().equalsIgnoreCase("plugin-install")) {
			Message r = Listener.nack(m, 4, "No plugin-install element detected.");
			r.setRecipient(originator);
			com.distribute(r);
			return false;
		}
		
		NamedNodeMap nnp = pluginInstallNode.getAttributes();
		Node uriNode = nnp.getNamedItem("uri");
		
		if (uriNode == null) {
			Message r = Listener.nack(m, 4, "No uri attribute detected.");
			r.setRecipient(originator);
			com.distribute(r);
			return false;
		}
		
		String uriValue = uriNode.getNodeValue();
		String pluginName = null;
		String[] tokens = uriValue.split(delims);
		for(int j = 0; j < tokens.length; j++){
			if(tokens[j].equalsIgnoreCase("jar")){
				pluginName = tokens[j-1];
			}
		}
		if(pluginName == null) {
			Message r = Listener.nack(m, 4, "Unable to get plugin name from uri.");
			r.setRecipient(originator);
			com.distribute(r);
			return false;
		}
		
		// See if we can fetch the bits from the remote URL file. If we can't stop now.
		if (!storeJar(pluginName, uriValue)) {
			Message r = Listener.nack(m, 4, "Unable to retrieve plugin from: " + uriValue + ".");
			r.setRecipient(originator);
			com.distribute(r);
			return false;
		}
		
		// Ask PluginManager to add the plugin. Use a (now-stored) different URL
		try {
			File file = new File (pluginStorage);
			URL url = new URL (file.toURI().toURL(), pluginName);
			PersistentPluginManager pm = ServerContext.getPluginManager();
			
			if (!pm.add(pluginName, url)) {
				Message r = Listener.nack(m, 4, "Unable to add Plugin.");  // low severity level
				r.setRecipient(originator);
				com.distribute(r);
				return false;
			}
			
			if(!pm.updatePlugin(pluginName)) {
				Message r = Listener.nack(m, 4, "Unable to add Plugin to persistent database.");  // low severity level
				r.setRecipient(originator);
				com.distribute(r);
				return false;
			}
		
			// Broadcast plugins to everyone. Ask PluginManager for collection of plugins, 
			// and form this request from this set.
	        StringBuilder sb = new StringBuilder("<request version='1.0' id='1'><plugins>");
	        for (Plugin plugin : pm.getList()) {
	        	sb.append ("<plugin name=\"" + plugin.name + "\" uri=\"" + plugin.location + "\"/>\n");
	        }
	        sb.append("</plugins></request>");
	        Document d = Message.construct(sb.toString());
	        
	        // send to everyone INCLUDING the originator of the Message m.
	        Message r = new Message (d);
	        r.setBroadcast();
	        com.distribute(r);
		} catch (Exception e) {
			e.printStackTrace();
			
			Message r = Listener.nack(m, 4, "Unable to add Plugin: " + e.getMessage());  // low severity level
			r.setRecipient(originator);
			com.distribute(r);
			return false;
		}
		
		Message r = Listener.ack(m, "Plugin installed successfully");
		r.setRecipient(originator);
		com.distribute(r);
		return true;
	}
	/**
	 * Try to fetch the plugin and store in our local storage.
	 * 
	 * @param pluginName
	 * @param uriValue
	 * @return
	 */
	private boolean storeJar(String pluginName, String uriValue) {
		URL pluginBits;
		
		try {
			pluginBits = new URL(uriValue);
		} catch (Exception e) {
			return false;
		}
		
		try {
			File f = new File (pluginStorage, pluginName + ".jar");
			
			FileOutputStream fos = new FileOutputStream (f);
			
			// grab all bits from input stream and store in our output Stream
			InputStream is = pluginBits.openStream();
			
			byte[] chunk = new byte[4096];
			int numRead;
			while ((numRead = is.read(chunk)) > 0) {
				fos.write(chunk, 0, numRead);
			}
			
			// done
			fos.close();
			is.close();
			
		} catch (Exception e) {
			// unable to store or create
			return false;
		}
		
		// done
		return true;
	}
}
